#pragma once
#include "oj_model.hpp"
#include <algorithm>
#include "oj_view.hpp"
#include <jsoncpp/json/json.h>
namespace ns_control
{
    using namespace std;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    // 主机
    class Machine
    {
    public:
        // 提供编译服务主机的ip
        std::string ip;
        // 提供编译服务主机port
        int port;
        // 提供编译服务主机负载
        uint64_t load;
        // mutex禁止拷贝的，使用指针
        std::mutex *mtx;

        // 1.提升负载
        void IncLoad()
        {
            // 加锁
            if (mtx != nullptr)
                unique_lock<mutex> lock(*mtx);
            load++;
        }
        // 2.减少负载
        void DecLoad()
        {
            // 加锁
            if (mtx != nullptr)
                unique_lock<mutex> lock(*mtx);
            load--;
        }
        // 3.重置负载
        void ResetLoad()
        {
            // 加锁
            if (mtx != nullptr)
                unique_lock<mutex> lock(*mtx);
            load = 0;
        }
        // 获取负载情况

        uint64_t Load()
        {
            if (mtx != nullptr)
                unique_lock<mutex> lock(*mtx);
            return load;
        }
    };

    const std::string service_machine = "./conf/service_machine.conf";
    
    //负载均衡器
    class LoadBlance
    {
    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
        }
        ~LoadBlance() {}
        // 1.加载配置文件中的所有机器
        bool LoadConf(const std::string &machine_conf)
        {
            ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(FATAL, "加载主机配置文件失败\n");
                return false;
            }
            string line;
            while (getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, ":");

                if (tokens.size() != 2)
                {
                    LOG(DEBUG, "%s 加载失败\n", line.c_str());
                    continue;
                }
                Machine m;

                m.ip = tokens[0];
                m.port = atoi(tokens[1].c_str());
                m.load = 0;
                m.mtx = new std::mutex();

                online.push_back(machines.size());
                // 加载之后不会去改变它，只操作 在线和 离线队列 machines用来定位主机
                machines.push_back(m);
            }

            in.close();
            return true;
        }
        // 2.负载均衡式的分配主机
        bool SmartChoice(int *id, Machine **m)
        {
            unique_lock<mutex> lock(_mutex);

            int online_num = online.size();
            if (online_num == 0)
            {
                LOG(FATAL, "所有主机全部离线!\n");
                return false;
            }

            // 采用轮询方式找到负载最小的主机
            *id = online[0];
            *m = &machines[online[0]];
            uint64_t minLoad = machines[online[0]].Load();
            for (int i = 1; i < online_num; ++i)
            {
                uint64_t curLoad = machines[online[i]].Load();
                if (curLoad < minLoad)
                {
                    minLoad = curLoad;
                    *id = online[i];
                    *m = &machines[online[i]];
                }
            }

            return true;
        }
        // 3.离线某台主机
        void OfflineMachine(int which)
        {
            unique_lock<mutex> lock(_mutex);
            for (auto it = online.begin(); it != online.end(); ++it)
            {
                if ((*it) == which)
                {
                    // 1.重置其负载
                    machines[which].ResetLoad();
                    // 2.移除在线队列
                    online.erase(it);
                    // 3.加入离线队列
                    offline.emplace_back(which);
                    break;
                }
            }
        }
        // 4.上线所有主机
        void OnlineMachine()
        {
            unique_lock<mutex> lock(_mutex);
            // 将所有主机全部放入在线队列
            online.insert(online.begin(), offline.begin(), offline.end());
            offline.erase(offline.begin(), offline.end());

            LOG(INFO, "上线所有主机\n");
        }

        void ShowMachines()
        {
            unique_lock<mutex> lock(_mutex);
            std::cout << "当前在线主机列表: ";
            for (auto &id : online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            std::cout << "当前离线主机列表: ";
            for (auto &id : offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
        }

    private:
        // 1.所有机器的容器
        vector<Machine> machines;
        // 2.所有在线的机器的下标
        vector<int> online;
        // 3.所有离线机器的下标
        vector<int> offline;

        mutex _mutex;
    };

    class Control
    {
    public:
        Control() {}
        ~Control() {}
        bool AllQuestions(string *html)
        {
            vector<struct Question> all;
            if (_model.GetAllQuestions(&all))
            {
                sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2)
                     { return stoi(q1.number) < stoi(q2.number); });
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                return false;
            }

            return true;
        }

        bool Question(const string &number, string *html)
        {
            struct Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "获取题目失败";
                return false;
            }
            return true;
        }

        void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            // 1.先根据题号获取题目
            struct Question q;
            if (!_model.GetOneQuestion(number, &q))
            {
                LOG(ERROR, "获取题目失败\n");
            }

            // 1.1拼接code
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();

            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + "\n" + q.tail;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_value); // 这是发送给判题主机的
            // 2.根据负载均衡器获取一个主机, 主机可能离线了 需要循环获取
            while (true)
            {
                int id = 0;
                Machine *m = nullptr;
                if (!_loadBlance.SmartChoice(&id, &m))
                {
                    return;
                }

                m->IncLoad();
                Client cli(m->ip, m->port);
                LOG(INFO, " 选择主机成功, 主机id:%d详情ip:%s:port:%d当前主机的负载是:%llu\n", id, m->ip.c_str(), m->port, m->load);

                // 3.判题
                if (auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
                {
                    // 执行任务成功才break
                    if (res->status == 200)
                    {
                        *out_json = res->body;
                        m->DecLoad();
                        LOG(INFO, "请求编译服务完成\n");
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    // 请求服务失败，将主机放入离线队列中
                    LOG(INFO, " 选择主机离线, 主机id:%d详情ip:%s:port:%d\n", id, m->ip.c_str(), m->port);
                    _loadBlance.OfflineMachine(id);
                    _loadBlance.ShowMachines(); // 仅仅是为了用来调试
                }
            }
        }

    private:
        LoadBlance _loadBlance;
        Model _model;
        View _view;
    };
}